package com.example.iceman.stepcounterapp;

import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.util.Log;

/**
 * Created by iceman on 16/2/2.
 * 计步器功能类，主要包含计步和方向检测的功能
 */
public class StepCounter implements SensorEventListener {

    private Context mContext;
    private SensorManager sensorManager;
    private Sensor acceSensor;
    private StepChangeListener onStepListener;

    private StepCalc stepCalc;
    private float[] accelerometerValues = new float[3];

    private int monitorInterval;
    private int filterTime;
    private final String TAG = "StepCounter";
    private int currentZOritation = 0;
    private Sensor oritationSensor;

    /**
     * 将角度转化为方向字符串，包含8个方向
     *
     * @param orientation 0～360的角度值
     * @return
     */
    public static String getOrientationString(int orientation) {
        if (orientation >= 345 || orientation < 15) {
            return "正北";
        } else if (orientation >= 15 && orientation < 75) {
            return "东北";
        } else if (orientation >= 75 && orientation <= 105) {
            return "正东";
        } else if (orientation >= 105 && orientation < 165) {
            return "东南";
        } else if (orientation >= 165 && orientation < 195) {
            return "正南";
        } else if (orientation >= 195 && orientation < 255) {
            return "西南";
        } else if (orientation >= 255 && orientation < 285) {
            return "正西";
        } else if (orientation >= 285 && orientation < 345) {
            return "西北";
        } else {
            return "未知方向";
        }
    }

    public StepCounter(Context context) {
        this(context, 50, 300);
    }


    /**
     * 使用自定义参数来创建一个计步器
     *
     * @param context         android上下文环境
     * @param monitorInterval 传感器检测间隔，单位为毫秒
     * @param filterTime      两步之间的时间间隔，用来做滤波参数，过滤掉快速甩动手机等杂项的干扰
     */
    public StepCounter(Context context, int monitorInterval, int filterTime) {
        mContext = context;
        stepCalc = new StepCalc();
        this.monitorInterval = monitorInterval * 1000;
        this.filterTime = filterTime;
        init();
    }


    private void init() {
        sensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
        if (sensorManager != null) {
            oritationSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
            acceSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
            if (acceSensor == null) {
                Log.e(TAG, "加速度传感器获取失败，无法计步");
            }
        } else {
            Log.e(TAG, "传感器初始化失败");
        }

    }

    /**
     * 开始计数器
     */
    public void start() {
        if (acceSensor != null) {
            sensorManager.registerListener(this, acceSensor, monitorInterval);
            sensorManager.registerListener(this, oritationSensor, monitorInterval);
        }
    }

    /**
     * 停止计步
     */
    public void stop() {
        if (sensorManager != null) {
            sensorManager.unregisterListener(this);
        }
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {

    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        if (event.sensor.getType() == Sensor.TYPE_ORIENTATION) {
//            Log.i(TAG, "方向:" + event.values[0] + ":" + event.values[1] + ":" + event.values[2]);
            currentZOritation = (int) event.values[0];
        } else {
            accelerometerValues = event.values.clone();
            float gravityNew = (float) Math.sqrt(accelerometerValues[0] * accelerometerValues[0]
                    + accelerometerValues[1] * accelerometerValues[1] + accelerometerValues[2] * accelerometerValues[2]);
            if (stepCalc != null) {
                stepCalc.DetectorNewStep(gravityNew);
            }
        }

    }

    /**
     * 对加速度传感器获取的数据进行波峰处理类
     */
    class StepCalc {
        private StepChangeListener onStepListener;
        final int valueNum = 4;
        //用于存放计算阈值的波峰波谷差值
        float[] tempValue = new float[valueNum];
        //差值库，存放最近4次差值
        int tempCount = 0;
        //是否上升的标志位
        boolean isDirectionUp = false;
        //持续上升次数
        int continueUpCount = 0;
        //上一点的持续上升的次数，为了记录波峰的上升次数
        int continueUpFormerCount = 0;
        //上一点的状态，上升还是下降
        boolean lastStatus = false;
        //波峰值
        float peakOfWave = 0;
        //波谷值
        float valleyOfWave = 0;
        //此次波峰的时间
        long timeOfThisPeak = 0;
        //上次波峰的时间
        long timeOfLastPeak = 0;
        //当前的时间
        long timeOfNow = 0;
        //上次传感器的值
        float gravityOld = 0;
        //动态阈值需要动态的数据，这个值用于这些动态数据的阈值
        final float initialValue = (float) 1.3;
        //初始阈值
        float ThreadValue = (float) 2.0;

        public void setOnStepListener(StepChangeListener listener) {
            this.onStepListener = listener;
        }


        /**
         * 检测到一个波峰以后，判断其是否是一个有效的步子
         * 判断逻辑1:波峰和波谷的差值是否达到阀值
         * 判断逻辑2:距离上次波峰的时间是否达到要求，排除快速抖动
         *
         * @param values
         */
        public void DetectorNewStep(float values) {
            logByGui(values);
            if (gravityOld == 0) {
                gravityOld = values;
            } else {
                if (DetectorPeak(values, gravityOld)) {
                    timeOfLastPeak = timeOfThisPeak;
                    timeOfNow = System.currentTimeMillis();
                    onStepListener.onLog("f:" + ThreadValue + ",peak:" + peakOfWave + ",valley:" + valleyOfWave);
                    if (peakOfWave - valleyOfWave >= ThreadValue) {
                        onStepListener.onLog("Now:" + timeOfNow + ",LastPeak:" + timeOfLastPeak);
                        timeOfThisPeak = timeOfNow;
                        if (timeOfNow - timeOfLastPeak >= filterTime) {
//                            calculateOrientation();
                            onStepListener.onStep(currentZOritation);
                            onStepListener.onLog("step!!!!!!!!!!!!!!!!!!!!!!");
                        }
                    }
                    if (timeOfNow - timeOfLastPeak >= filterTime
                            && (peakOfWave - valleyOfWave >= initialValue)) {
                        timeOfThisPeak = timeOfNow;
                        ThreadValue = Peak_Valley_Thread(peakOfWave - valleyOfWave);
                    }
                }
            }
            gravityOld = values;
        }

        private void logByGui(float values) {

            StringBuilder builder = new StringBuilder();
            int m = (int) (values * 10) - 90;
            for (int i = 0; i < m; i++) {
                builder.append("▋");
            }
            Log.e(TAG, String.format("%.1f", values) + builder.toString());
        }

        /**
         * 检测波峰
         * 以下三个条件判断为波峰：
         * 1.目前点为下降的趋势：isDirectionUp为false
         * 2.之前的点为上升的趋势：lastStatus为true
         * 3.到波峰为止，持续上升大于等于3次，排除抖动
         * 记录波谷值
         * 1.观察波形图，可以发现在出现步子的地方，波谷的下一个就是波峰，有比较明显的特征以及差值
         * 2.所以要记录每次的波谷值，为了和下次的波峰做对比
         */
        public boolean DetectorPeak(float newValue, float oldValue) {
            lastStatus = isDirectionUp;
            if (newValue >= oldValue) {
                isDirectionUp = true;
                continueUpCount++;
                onStepListener.onLog("up:" + oldValue + "->" + newValue + ":" + continueUpCount);
            } else {
                continueUpFormerCount = continueUpCount;
                continueUpCount = 0;
                isDirectionUp = false;
                onStepListener.onLog("down:" + oldValue + "->" + newValue + ":" + continueUpCount);
            }

            if (!isDirectionUp && lastStatus
                    && (continueUpFormerCount >= 3)) {
                peakOfWave = oldValue;
                return true;
            } else if (!lastStatus && isDirectionUp) {
                valleyOfWave = oldValue;
                return false;
            } else {
                return false;
            }
        }

        /**
         * 阈值的计算
         * 1.通过波峰波谷的差值计算阈值
         * 2.记录4个值，存入tempValue[]数组中
         * 3.在将数组传入函数averageValue中计算阈值
         */
        public float Peak_Valley_Thread(float value) {
            float tempThread = ThreadValue;
            if (tempCount < valueNum) {
                tempValue[tempCount] = value;
                tempCount++;
            } else {
                tempThread = averageValue(tempValue, valueNum);
                for (int i = 1; i < valueNum; i++) {
                    tempValue[i - 1] = tempValue[i];
                }
                tempValue[valueNum - 1] = value;
            }
            return tempThread;

        }

        /**
         * 梯度化阈值，将最近4次波峰波谷差值保存起来
         * 1.计算数组的均值
         * 2.通过均值将阈值梯度化在一个范围里
         */
        public float averageValue(float value[], int n) {
            float ave = 0;
            for (int i = 0; i < n; i++) {
                ave += value[i];
            }
            ave = ave / valueNum;
            if (ave >= 8)
                ave = (float) 4.3;
            else if (ave >= 7 && ave < 8)
                ave = (float) 3.3;
            else if (ave >= 4 && ave < 7)
                ave = (float) 2.3;
            else if (ave >= 3 && ave < 4)
                ave = (float) 2.0;
            else {
                ave = (float) 1.3;
            }
            return ave;
        }
    }


    public void setOnStepListener(StepChangeListener listener) {
        onStepListener = listener;
        stepCalc.setOnStepListener(listener);
    }

}
